from khronos.utils import INF, Namespace
import time

class Experiment(object):
    """Simulation experiment class. An experiment is a set of related simulations using the same 
    model. A set of results is accumulated, and an indicator namespace is also created, where the 
    user can accumulate results on the different simulations."""
    description = ""
    indicators = []
    simulator = None
    duration = INF
    
    def __init__(self, simulator=None, duration=None):
        self.simulator = simulator if simulator is not None else self.__class__.simulator
        self.duration  = duration  if duration  is not None else self.__class__.duration
        self.running = False
        self.simulations = []
        self.indicators = Namespace()
        
    def __call__(self):
        if self.simulator.is_running():
            raise Exception("cannot start experiment during an ongoing simulation")
        # Initialization
        self.running = True
        self.simulations = []
        self.__indicator_init()
        
        # Execution begins here
        self.__indicator_start()
        self.start()
        while self.running:
            self.__indicator_run_start()
            self.run_start()
            self.simulations.append(self.simulator.single_run(self.duration))
            self.__indicator_run_end()
            self.run_end()
        self.__indicator_end()
        self.end()
        
    def __indicator_init(self):
        """Initialize the Experiment object's indicator namespace, getting indicators from all 
        base classes. In case a class defines an indicator with the same name as an indicator in 
        one of its superclasses, the subclass' indicator overrides the superclass'."""
        self.indicators.clear()
        for cls in reversed(self.__class__.mro()):
            if hasattr(cls, "indicators"):
                for indicator in getattr(cls, "indicators"):
                    if isinstance(indicator, tuple):
                        name, indicator = indicator
                        self.indicators[name] = indicator(self)
                    else:
                        self.indicators[indicator.__name__] = indicator(self)
                        
    def __indicator_start(self):
        for indicator in self.indicators.itervalues():
            indicator.start()
            
    def __indicator_end(self):
        for indicator in self.indicators.itervalues():
            indicator.end()
            
    def __indicator_run_start(self):
        for indicator in self.indicators.itervalues():
            indicator.run_start()
            
    def __indicator_run_end(self):
        for indicator in self.indicators.itervalues():
            indicator.run_end()
            
    def start(self): pass
    def end(self): pass
    def run_start(self): pass
    def run_end(self): pass
    
    def report(self):
        """Build and return a report of the experiment."""
        raise NotImplementedError()
        
